﻿/*! @file */
/*
	Copyright (C) 2008, kobake
	Copyright (C) 2018-2022, Sakura Editor Organization

	SPDX-License-Identifier: Zlib
*/

#include "StdAfx.h"
#include <limits.h>
#include "CEditView.h"
#include "window/CEditWnd.h"
#include "parse/CWordParse.h"
#include "util/string_ex2.h"
#include "CSelectLang.h"
#include "String_define.h"

const int STRNCMP_MAX = 100;	/* MAXキーワード長：_strnicmp文字列比較最大値(CEditView::KeySearchCore) */	// 2006.04.10 fon

// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
//                           検索                              //
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //

/*! キーワード辞書検索の前提条件チェックと、検索

	@date 2006.04.10 fon OnTimer, CreatePopUpMenu_Rから分離
*/
BOOL CEditView::KeyWordHelpSearchDict( LID_SKH nID, POINT* po, RECT* rc )
{
	CNativeW	cmemCurText;

	/* キーワードヘルプを使用するか？ */
	if( !m_pTypeData->m_bUseKeyWordHelp )	/* キーワードヘルプ機能を使用する */	// 2006.04.10 fon
		goto end_of_search;
	/* フォーカスがあるか？ */
	if( !GetCaret().ExistCaretFocus() ) 
		goto end_of_search;
	/* ウィンドウ内にマウスカーソルがあるか？ */
	GetCursorPos( po );
	GetWindowRect( GetHwnd(), rc );
	if( !PtInRect( rc, *po ) )
		goto end_of_search;
	switch(nID){
	case LID_SKH_ONTIMER:
		/* 右コメントの１～３でない場合 */
		if(!( m_bInMenuLoop == FALSE	&&			/* １．メニュー モーダル ループに入っていない */
			0 != m_dwTipTimer			&&			/* ２．辞書Tipを表示していない */
			300 < ::GetTickCount() - m_dwTipTimer	/* ３．一定時間以上、マウスが固定されている */
		) )	goto end_of_search;
		break;
	case LID_SKH_POPUPMENU_R:
		if(!( m_bInMenuLoop == FALSE	//&&			/* １．メニュー モーダル ループに入っていない */
		//	0 != m_dwTipTimer			&&			/* ２．辞書Tipを表示していない */
		//	1000 < ::GetTickCount() - m_dwTipTimer	/* ３．一定時間以上、マウスが固定されている */
		) )	goto end_of_search;
		break;
	default:
		PleaseReportToAuthor( nullptr, L"CEditView::KeyWordHelpSearchDict\nnID=%d", (int)nID );
	}
	/* 選択範囲のデータを取得(複数行選択の場合は先頭の行のみ) */
	if( GetSelectedDataOne( cmemCurText, STRNCMP_MAX + 1 ) ){
	}
	/* キャレット位置の単語を取得する処理 */	// 2006.03.24 fon
	else if(GetDllShareData().m_Common.m_sSearch.m_bUseCaretKeyWord){
		if(!GetParser().GetCurrentWord(&cmemCurText))
			goto end_of_search;
	}
	else
		goto end_of_search;

	if( CNativeW::IsEqual( cmemCurText, m_cTipWnd.m_cKey ) &&	/* 既に検索済みか */
		(!m_cTipWnd.m_KeyWasHit) )								/* 該当するキーがなかった */
		goto end_of_search;
	m_cTipWnd.m_cKey = cmemCurText;

	/* 検索実行 */
	if( !KeySearchCore(&m_cTipWnd.m_cKey) )
		goto end_of_search;
	m_dwTipTimer = 0;		/* 辞書Tipを表示している */
	m_poTipCurPos = *po;	/* 現在のマウスカーソル位置 */
	return TRUE;			/* ここまで来ていればヒット・ワード */

	/* キーワードヘルプ表示処理終了 */
	end_of_search:
	return FALSE;
}

/*! キーワード辞書検索処理メイン

	@date 2006.04.10 fon KeyWordHelpSearchDictから分離
*/
BOOL CEditView::KeySearchCore( const CNativeW* pcmemCurText )
{
	CNativeW*	pcmemRefKey;
	int			nCmpLen = STRNCMP_MAX; // 2006.04.10 fon
	int			nLine; // 2006.04.10 fon

	m_cTipWnd.m_cInfo.SetString( L"" );	/* tooltipバッファ初期化 */
	/* 1行目にキーワード表示の場合 */
	if(m_pTypeData->m_bUseKeyHelpKeyDisp){	/* キーワードも表示する */	// 2006.04.10 fon
		m_cTipWnd.m_cInfo.AppendString( L"[ " );
		m_cTipWnd.m_cInfo.AppendString( pcmemCurText->GetStringPtr() );
		m_cTipWnd.m_cInfo.AppendString( L" ]" );
	}
	/* 途中まで一致を使う場合 */
	if(m_pTypeData->m_bUseKeyHelpPrefix)
		nCmpLen = wcslen( pcmemCurText->GetStringPtr() );	// 2006.04.10 fon
	m_cTipWnd.m_KeyWasHit = FALSE;
	for(int i =0 ; i < m_pTypeData->m_nKeyHelpNum; i++){	//最大数：MAX_KEYHELP_FILE
		if( m_pTypeData->m_KeyHelpArr[i].m_bUse ){
			// 2006.04.10 fon (nCmpLen,pcmemRefKey,nSearchLine)引数を追加
			CNativeW*	pcmemRefText;
			int nSearchResult=m_cDicMgr.CDicMgr::Search(
				pcmemCurText->GetStringPtr(),
				nCmpLen,
				&pcmemRefKey,
				&pcmemRefText,
				m_pTypeData->m_KeyHelpArr[i].m_szPath,
				&nLine
			);
			/* 該当するキーがある */
			if( nSearchResult ){
				/* 有効になっている辞書を全部なめて、ヒットの都度説明の継ぎ増し */
				if(m_pTypeData->m_bUseKeyHelpAllSearch){	/* ヒットした次の辞書も検索 */	// 2006.04.10 fon
					/* バッファに前のデータが詰まっていたらseparator挿入 */
					if(m_cTipWnd.m_cInfo.GetStringLength() != 0)
						m_cTipWnd.m_cInfo.AppendString( LS(STR_ERR_DLGEDITVW5) );
					else
						m_cTipWnd.m_cInfo.AppendString( LS(STR_ERR_DLGEDITVW6) );	/* 先頭の場合 */
					/* 辞書のパス挿入 */
					{
						WCHAR szFile[MAX_PATH];
						// 2013.05.08 表示するのはファイル名(拡張子なし)のみにする
						_wsplitpath( m_pTypeData->m_KeyHelpArr[i].m_szPath, nullptr, nullptr, szFile, nullptr );
						m_cTipWnd.m_cInfo.AppendString( szFile );
					}
					m_cTipWnd.m_cInfo.AppendString( L"\n" );
					/* 前方一致でヒットした単語を挿入 */
					if(m_pTypeData->m_bUseKeyHelpPrefix){	/* 選択範囲で前方一致検索 */
						m_cTipWnd.m_cInfo.AppendString( pcmemRefKey->GetStringPtr() );
						m_cTipWnd.m_cInfo.AppendString( L" >>\n" );
					}

					/* 調査した「意味」を挿入 */
					m_cTipWnd.m_cInfo.AppendString( UnEscapeInfoText( *pcmemRefText ) );

					delete pcmemRefText;
					delete pcmemRefKey;	// 2006.07.02 genta
					/* タグジャンプ用の情報を残す */
					if(!m_cTipWnd.m_KeyWasHit){
						m_cTipWnd.m_nSearchDict=i;	/* 辞書を開くとき最初にヒットした辞書を開く */
						m_cTipWnd.m_nSearchLine=nLine;
						m_cTipWnd.m_KeyWasHit = TRUE;
					}
				}
				else{	/* 最初のヒット項目のみ返す場合 */
					/* キーワードが入っていたらseparator挿入 */
					if(m_cTipWnd.m_cInfo.GetStringLength() != 0)
						m_cTipWnd.m_cInfo.AppendString( L"\n--------------------\n" );
					
					/* 前方一致でヒットした単語を挿入 */
					if(m_pTypeData->m_bUseKeyHelpPrefix){	/* 選択範囲で前方一致検索 */
						m_cTipWnd.m_cInfo.AppendString( pcmemRefKey->GetStringPtr() );
						m_cTipWnd.m_cInfo.AppendString( L" >>\n" );
					}
					
					/* 調査した「意味」を挿入 */
					m_cTipWnd.m_cInfo.AppendString( UnEscapeInfoText( *pcmemRefText ) );

					delete pcmemRefText;
					delete pcmemRefKey;	// 2006.07.02 genta
					/* タグジャンプ用の情報を残す */
					m_cTipWnd.m_nSearchDict=i;
					m_cTipWnd.m_nSearchLine=nLine;
					m_cTipWnd.m_KeyWasHit = TRUE;
					return TRUE;
				}
			}
		}
	}
	if( m_cTipWnd.m_KeyWasHit != FALSE ){
			return TRUE;
	}
	/* 該当するキーがなかった場合 */
	return FALSE;
}

bool CEditView::MiniMapCursorLineTip( POINT* po, RECT* rc, bool* pbHide )
{
	*pbHide = true;
	if( !m_bMiniMap ){
		return false;
	}
	// ウィンドウ内にマウスカーソルがあるか？
	GetCursorPos( po );
	GetWindowRect( GetHwnd(), rc );
	rc->right -= ::GetSystemMetrics(SM_CXVSCROLL);
	if( !PtInRect( rc, *po ) ){
		return false;
	}
	if(!( m_bInMenuLoop == FALSE	&&			/* １．メニュー モーダル ループに入っていない */
		300 < ::GetTickCount() - m_dwTipTimer	/* ２．一定時間以上、マウスが固定されている */
	) ){
		return false;
	}
	if( WindowFromPoint( *po ) != GetHwnd() ){
		return false;
	}

	CMyPoint ptClient(*po);
	ScreenToClient( GetHwnd(), &ptClient );
	CLayoutPoint ptNew;
	GetTextArea().ClientToLayout( ptClient, &ptNew );
	// 同じ行ならなにもしない
	if( 0 == m_dwTipTimer && m_cTipWnd.m_nSearchLine == (Int)ptNew.y ){
		*pbHide = false; // 表示継続
		return false;
	}
	CNativeW cmemCurText;
	CLayoutYInt nTipBeginLine = ptNew.y;
	CLayoutYInt nTipEndLine = ptNew.y + CLayoutYInt(4);
	for( CLayoutYInt nCurLine = nTipBeginLine; nCurLine < nTipEndLine; nCurLine++ ){
		const CLayout* pcLayout = nullptr;
		if( 0 <= nCurLine ){
			pcLayout = GetDocument()->m_cLayoutMgr.SearchLineByLayoutY( nCurLine );
		}
		if( pcLayout ){
			CNativeW cmemCurLine;
			{
				CLogicInt nLineLen = pcLayout->GetLengthWithoutEOL();
				const wchar_t* pszData = pcLayout->GetPtr();
				int nLimitLength = 80;
				int pre = 0;
				int i = 0;
				int k = 0;
				int charSize = CNativeW::GetSizeOfChar( pszData, nLineLen, i );
				int charWidth = t_max(1, (int)(Int)CNativeW::GetKetaOfChar( pszData, nLineLen, i ));
				int charType = 0;
				// 連続する"\t" " " を " "1つにする
				// 左からnLimitLengthまでの幅を切り取り
				while( i + charSize <= (Int)nLineLen && k + charWidth <= nLimitLength ){
					if( pszData[i] == L'\t' || pszData[i] == L' ' ){
						if( charType == 0 ){
							cmemCurLine.AppendString( pszData + pre , i - pre );
							cmemCurLine.AppendString( L" " );
							charType = 1;
						}
						pre = i + charSize;
						k++;
					}else{
						k += charWidth;
						charType = 0;
					}
					i += charSize;
					charSize = CNativeW::GetSizeOfChar( pszData, nLineLen, i );
					charWidth = t_max(1, (int)(Int)CNativeW::GetKetaOfChar( pszData, nLineLen, i ));
				}
				cmemCurLine.AppendString( pszData + pre , i - pre );
			}
			if( nTipBeginLine != nCurLine ){
				cmemCurText.AppendString( L"\n" );
			}
			cmemCurLine.Replace( L"\\", L"\\\\" );
			cmemCurText.AppendNativeData( cmemCurLine );
		}
	}
	if( cmemCurText.GetStringLength() <= 0 ){
		return false;
	}
	m_cTipWnd.m_cKey = cmemCurText;
	m_cTipWnd.m_cInfo = cmemCurText.GetStringPtr();
	m_cTipWnd.m_nSearchLine = (Int)ptNew.y;
	m_dwTipTimer = 0;		// 辞書Tipを表示している */
	m_poTipCurPos = *po;	// 現在のマウスカーソル位置 */
	return true;			// ここまで来ていればヒット・ワード
}

/* 現在カーソル位置単語または選択範囲より検索等のキーを取得 */
void CEditView::GetCurrentTextForSearch( CNativeW& cmemCurText, bool bStripMaxPath /* = true */, bool bTrimSpaceTab /* = false */ )
{
	int				i;
	CNativeW		cmemTopic = L"";
	const wchar_t*	pLine;
	CLogicInt		nLineLen;
	CLogicInt		nIdx;
	CLayoutRange	sRange;

	cmemCurText.SetString(L"");
	if( GetSelectionInfo().IsTextSelected() ){	/* テキストが選択されているか */
		/* 選択範囲のデータを取得 */
		if( GetSelectedDataOne( cmemCurText, INT_MAX ) ){
			/* 検索文字列を現在位置の単語で初期化 */
			if( bStripMaxPath ){
				LimitStringLengthW(cmemCurText.GetStringPtr(), cmemCurText.GetStringLength(), _MAX_PATH - 1, cmemTopic);
			}else{
				cmemTopic = cmemCurText;
			}
		}
	}else{
		const CLayout*	pcLayout;
		pLine = m_pcEditDoc->m_cLayoutMgr.GetLineStr( GetCaret().GetCaretLayoutPos().GetY2(), &nLineLen, &pcLayout );
		if( nullptr != pLine ){
			/* 指定された桁に対応する行のデータ内の位置を調べる */
			nIdx = LineColumnToIndex( pcLayout, GetCaret().GetCaretLayoutPos().GetX2() );

			/* 現在位置の単語の範囲を調べる */
			bool bWhere = m_pcEditDoc->m_cLayoutMgr.WhereCurrentWord(
				GetCaret().GetCaretLayoutPos().GetY2(),
				nIdx,
				&sRange,
				nullptr,
				nullptr
			);
			if( bWhere ){
				/* 選択範囲の変更 */
				GetSelectionInfo().m_sSelectBgn = sRange;
				GetSelectionInfo().m_sSelect    = sRange;

				/* 選択範囲のデータを取得 */
				if( GetSelectedDataOne( cmemCurText, INT_MAX ) ){
					/* 検索文字列を現在位置の単語で初期化 */
					if( bStripMaxPath ){
						LimitStringLengthW(cmemCurText.GetStringPtr(), cmemCurText.GetStringLength(), _MAX_PATH - 1, cmemTopic);
					}else{
						cmemTopic = cmemCurText;
					}
				}
				/* 現在の選択範囲を非選択状態に戻す */
				GetSelectionInfo().DisableSelectArea( false );
			}
		}
	}

	wchar_t *pTopic2 = cmemTopic.GetStringPtr();
	if( bTrimSpaceTab ){
		// 前のスペース・タブを取り除く
		while( L'\0' != *pTopic2 && ( ' ' == *pTopic2 || '\t' == *pTopic2 ) ){
			pTopic2++;
		}
	}
	int nTopic2Len = (int)wcslen( pTopic2 );
	/* 検索文字列は改行まで */
	bool bExtEol = GetDllShareData().m_Common.m_sEdit.m_bEnableExtEol;
	for( i = 0; i < nTopic2Len; ++i ){
		if( WCODE::IsLineDelimiter(pTopic2[i], bExtEol) ){
			break;
		}
	}
	
	if( bTrimSpaceTab ){
		// 後ろのスペース・タブを取り除く
		int m = i - 1;
		while( 0 <= m &&
		    ( L' ' == pTopic2[m] || L'\t' == pTopic2[m] ) ){
			m--;
		}
		if( 0 <= m ){
			i = m + 1;
		}
	}
	cmemCurText.SetString( pTopic2, i );
}

/*!	現在カーソル位置単語または選択範囲より検索等のキーを取得（ダイアログ用）
	@return 値を設定したか
	@date 2006.08.23 ryoji 新規作成
	@date 2014.07.01 Moca bGetHistory追加、戻り値をboolに変更
*/
bool CEditView::GetCurrentTextForSearchDlg( CNativeW& cmemCurText, bool bGetHistory )
{
	bool bStripMaxPath = false;
	cmemCurText.SetString(L"");

	if( GetSelectionInfo().IsTextSelected() ){	// テキストが選択されている
		GetCurrentTextForSearch( cmemCurText, bStripMaxPath );
	}
	else{	// テキストが選択されていない
		bool bGet = false;
		if( GetDllShareData().m_Common.m_sSearch.m_bCaretTextForSearch ){
			GetCurrentTextForSearch( cmemCurText, bStripMaxPath );	// カーソル位置単語を取得
			if( cmemCurText.GetStringLength() == 0 && bGetHistory ){
				bGet = true;
			}
		}else{
			bGet = true;
		}
		if( bGet ){
			if( 0 < GetDllShareData().m_sSearchKeywords.m_aSearchKeys.size()
					&& m_nCurSearchKeySequence < GetDllShareData().m_Common.m_sSearch.m_nSearchKeySequence ){
				cmemCurText.SetString( GetDllShareData().m_sSearchKeywords.m_aSearchKeys[0] );	// 履歴からとってくる
				return true; // ""でもtrue
			}else{
				cmemCurText.SetString( m_strCurSearchKey.c_str() );
				return 0 <= m_nCurSearchKeySequence; // ""でもtrue.未設定のときはfalse
			}
		}
	}
	return 0 < cmemCurText.GetStringLength();
}

// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //
//                        描画用判定                           //
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- //

/* 現在位置が検索文字列に該当するか */
//2002.02.08 hor
//正規表現で検索したときの速度改善のため、マッチ先頭位置を引数に追加
//Jun. 26, 2001 genta	正規表現ライブラリの差し替え
/*
	@retval 0
		(パターン検索時) 指定位置以降にマッチはない。
		(それ以外) 指定位置は検索文字列の始まりではない。
	@retval 1,2,3,...
		(パターン検索時) 指定位置以降にマッチが見つかった。
		(単語検索時) 指定位置が検索文字列に含まれる何番目の単語の始まりであるか。
		(それ以外) 指定位置が検索文字列の始まりだった。
*/
int CEditView::IsSearchString(
	const CStringRef&	cStr,
	/*
	const wchar_t*	pszData,
	CLogicInt		nDataLen,
	*/
	CLogicInt		nPos,
	CLogicInt*		pnSearchStart,
	CLogicInt*		pnSearchEnd
) const
{
	*pnSearchStart = nPos;	// 2002.02.08 hor

	if( m_sCurSearchOption.bRegularExp ){
		/* 行頭ではない? */
		/* 行頭検索チェックは、CBregexpクラス内部で実施するので不要 2003.11.01 かろと */

		/* 位置を0でMatchInfo呼び出すと、行頭文字検索時に、全て true　となり、
		** 画面全体が検索文字列扱いになる不具合修正
		** 対策として、行頭を MacthInfoに教えないといけないので、文字列の長さ・位置情報を与える形に変更
		** 2003.05.04 かろと
		*/
		if( m_CurRegexp.Match( cStr.GetPtr(), cStr.GetLength(), nPos ) ){
			*pnSearchStart = m_CurRegexp.GetIndex();	// 2002.02.08 hor
			*pnSearchEnd = m_CurRegexp.GetLastIndex();
			return 1;
		}
		else{
			return 0;
		}
	}
	else if( m_sCurSearchOption.bWordOnly ) { // 単語検索
		/* 指定位置の単語の範囲を調べる */
		CLogicInt posWordHead, posWordEnd;
		if( ! CWordParse::WhereCurrentWord_2( cStr.GetPtr(), CLogicInt(cStr.GetLength()), nPos, GetDllShareData().m_Common.m_sEdit.m_bEnableExtEol, &posWordHead, &posWordEnd, nullptr, nullptr ) ) {
			return 0; // 指定位置に単語が見つからなかった。
 		}
		if( nPos != posWordHead ) {
			return 0; // 指定位置は単語の始まりではなかった。
		}
		const CLogicInt wordLength = posWordEnd - posWordHead;
		const wchar_t *const pWordHead = cStr.GetPtr() + posWordHead;

		// 比較関数
		auto fcmp = m_sCurSearchOption.bLoHiCase ? wcsncmp : _wcsnicmp;

		// 検索語を単語に分割しながら指定位置の単語と照合する。
		int wordIndex = 0;
		const wchar_t* const searchKeyEnd = m_strCurSearchKey.data() + m_strCurSearchKey.size();
		for( const wchar_t* p = m_strCurSearchKey.data(); p < searchKeyEnd; ) {
			CLogicInt begin, end; // 検索語に含まれる単語?の位置。WhereCurrentWord_2()の仕様では空白文字列も単語に含まれる。
			if( CWordParse::WhereCurrentWord_2( p, CLogicInt(searchKeyEnd - p), CLogicInt(0), GetDllShareData().m_Common.m_sEdit.m_bEnableExtEol, &begin, &end, nullptr, nullptr )
				&& begin == 0 && begin < end
			) {
				if( ! WCODE::IsWordDelimiter( *p ) ) {
					++wordIndex;
					// p...(p + end) が検索語に含まれる wordIndex番目の単語。(wordIndexの最初は 1)
					if( wordLength == end && 0 == fcmp( p, pWordHead, wordLength ) ) {
						*pnSearchStart = posWordHead;
						*pnSearchEnd = posWordEnd;
						return wordIndex;
					}
				}
				p += end;
			} else {
				p += CNativeW::GetSizeOfChar( p, searchKeyEnd - p, 0 );
			}
		}
		return 0; // 指定位置の単語と検索文字列に含まれる単語は一致しなかった。
	}
	else {
		const wchar_t* pHit = CSearchAgent::SearchString(cStr.GetPtr(), cStr.GetLength(), nPos, m_sSearchPattern);
		if( pHit ){
			*pnSearchStart = pHit - cStr.GetPtr();
			*pnSearchEnd = *pnSearchStart + m_sSearchPattern.GetLen();
			return 1;
		}
		return 0; // この行はヒットしなかった
	}
	return 0;
}
